查看原文
其他

有赞移动助手App 本地抓包方案

有赞技术 有赞coder 2021-10-12

文 | 杨彬 on 电商技术

一、概述

有赞移动助手(下面简称助手App)网关切换功能(有赞移动 App 一键切换网关实践),在我们有赞移动整个开发,测试回归,产品回归验收等扮演着重要的角色,近期我们在 App 集成了本地抓包的功能,更是如虎添翼。在测试抓包,线上问题排查等场景发挥着重要的作用。

二、背景

在目前的抓包的场景中,大部分通过手机连接 PC,进行 IP 代理,用三方抓包工具(Charles,Fiddler,Wireshark等)进行抓包,具体有如下痛点:

  • 抓包需要将移动设备的 Wi-Fi 设置中将代理手动设置成 PC 的 IP,和对应的端口号,过程比较繁琐

  • 移动设备需要安装 Charles 提供的 Root 证书

  • 不能随时随地的用移动设备抓包,必须强依赖于 PC 端

因此我们需要在App增加本地抓包的功能, 通过一种技术手段可以实时监听到通过移动助手网关功能连接到 ZanProxy  服务器的所有网络请求,经过调研,实现这个功能应该具备以下几个条件:

  • 利用 Socket 协议来实现消息推送经过助手 App 网关功能的网络请求到客户端

  • 当我们切换到其他 APP 进行网络请求时,要让助手 App 能在后台保活,甚至是常驻在后台,来达到对网络监听的目的

  • 需要在有赞助手有对应的页面去展示监听到的网络请求,header,response,request等数据

三、有赞移动助手

我们助手App网关功能的原理是 Android 提供的 VpnService ,iOS的NetworkExtension 将 TCP 连接的 IP 数据包通过 tun2socks 转化成 socks5 代理,将数据转发到 ZanProxy 服务器中,实现整个网关功能。

3.1 tun2socks

tun2socks 实现一种机制,它可以让你无需改动任何应用程序而完全同名地机那个数据用 socks 协议封装,转发给一个 socks 代理,然后由代理程序负责于正式服务器之间转发应用数据。使用代理有两种方式,一种是显示配置代理,数据离开你的主机时它的目标地址就是代理服务器。另一种是做透明代理,即在中途把原始数据重定向到一个应用程序,由该代理程序代理转发。tun2socks 在第二种的基础上,完成了 socks 协议的封装,并且实现该机制时使用了强大的 tun 虚拟网卡而不必再去配置复杂的 iptables 规则,如下图所示

3.2 socks代理

socks 运作原理,就是在TCP数据外包一层 socks 协议头,到达 socks 代理服务器后,脱去 socks 头,然后通过 socks 服务器与真实服务器之间建立的连接将 TCP 数据传给真实服务器,socks 代理并不理解任何应用层协议,它只是负责转发应用层数据而已,这一点使 socks 成为了一个通用的代理协议,这一点和 HTTP 代理服务器是完全不同。

四、技术方案

了解了整个流程以后我们回到最初的痛点,需要利用 Socket 协议来让服务器和App建立长链接,实时监听通过助手 App 网关的所有 http/https 请求

4.1 Websocket

WebSocket 是 HTML5 新增的一种通信协议。WebSocket 协议是一种持久化的双向通信协议,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和 HTTP 最大的不同有两点:

  • WebSocket 是一种双向通信协议,在建立连接后,WebSocket 服务器和 Browser/UA 都能主动的向对方发送或接收数据,就像 Socket一样,不同的是 WebSocket 是一种建立在Web基础上的一种简单模拟 Socket 的协议。

  • WebSocket需要通过握手连接,类似于 TCP,它也需要客户端和服务器端进行握手连接,连接成功后才能相互通信。

下面是一个简单的建立握手的时序图:

4.2 Socket.IO

socket.io 是支持浏览器和服务器之间实时、双向基于事件通信的库,它包括 Node.js 服务端 API 和浏览器的 JavaScript 端的API,是一个完全由 JavaScript 实现、基于 Node.js、支持 WebSocket 协议的用于实时通信、跨平台的开源框架。底层是基于 engine.io,在此基础上增加了Namespace、room、自动重连等特性。

socket.io 设计的目标是支持任何的浏览器,任何 Mobile 设备。支持主流的 PC 浏览器 (IE,Safari,Chrome,Firefox,Opera等),Mobile 浏览器(iphone Safari/ipad Safari/Android WebKit/WebOS WebKit等)。socket.io旨在使实时应用在每个浏览器和移动设备上成为可能,模糊不同的传输机制之间的差异。

但是,WebSocket 协议是 HTML5 新推出的协议,浏览器对它的支持并不完善,由此可以看出,socket.io 不可能仅仅是对 WebSocket 的实现,它还支持其他的通信方式,如上面介绍过的 AJAX 轮询和 Long Polling。根据浏览器的支持程度,自主选择使用哪种方式进行通讯。

4.2.1 Socket.io支持的通信方式:

  • WebSocket

  • Adobe Flash Socket

  • AJAX long-polling

  • AJAX multipart streaming

  • Forever IFrame

  • JSONP polling

4.2.2 可靠性

socket.io即使在下列情况下也能建立联系:

  • 代理和负载平衡器

  • 个人防火墙和杀毒软件

4.2.3自动重连机制

除非有特殊指示,否则断开连接的客户端将尝试重新连接,直到服务器再次可用为止

4.2.4 断开检测

在 engine.io 级别实现心跳极值,允许服务器和客户端都知道对方何时不再响应。通过在服务器和客户端设置计时器,在握手连接期间共享超时值(pinginterval和pingTimeout参数),可以实现该功能。

4.2.5 多路复用

为了在应用程序中创建关注点分离(例如每个模块,或者基于权限),socket.io允许您创建多个名称空间,这些名称空间将作为单独的通信通道,但将共享相同的底层连接。

其他更多的包括,二进制的支持、房间支持等特性,由于想到以后的可扩展性以及客户端的支持程度,以及因此我们选择socket.io作为我们消息推送的技术方案。

4.2.6 源码分析

在建立连接后,每个客户端会被自动加入到一个默认的命名空间。在每个命名空间中,socket 会被默认加入两个名为 None 和 sid 的房间。None的房间用于广播,而 sid 是当前客户端的 session id,用于单播。除默认的房间外,我们可以根据需要将对应 socket 加入自定义房间,roomid 唯一即可。socket.io 基于engine.io,支持 websocket 和 long polling。如果是 long polling,会定时发送 GET, POST请求,当没有数据时,GET 请求在拉取队列消息时会 hang住(超时时间为 pingTimeout ),如果hang住期间服务器一直没有数据产生,则需要等到客户端发送下一个 POST 请求时,此时服务器会往队列中存储 POST 请求中的消息,这样上一个 GET 请求才会返回。如果upgrade到了 Websocket 连接,则探测成功之后会定期ping/pong来保活连接。流程如下图所示:

4.3 Socket.IO应用

在本次需求中移动端 iOS 用的开源库 socket.io-client-swift 是swift语言编写的。Andriod 端用的开源库 socket.io-android-chat 。下面以iOS为例看下如何使用:

  1. // MARK: Initializers


  2. /// Type safe way to create a new SocketIOClient. `opts` can be omitted.

  3. ///

  4. /// - parameter socketURL: The url of the socket.io server.

  5. /// - parameter config: The config for this socket.

  6. public init(socketURL: URL, config: SocketIOClientConfiguration = []) {

  7. self._config = config

  8. self.socketURL = socketURL


  9. super.init()


  10. setConfigs(_config)

  11. }


创建初始化 SocketManager 实例,指定 socketUrl ,指定对应的 nameSpace(名称空间)来保证单独的通信通道,将 UUID(设备唯一id)当作 cookie 添加到 header 中,保证能够识别是哪台设备和服务端建立的 socket 通信,调用 connect 方法进行 socket 连接。

  1. socket.on(clientEvent: .connect) { data, ack in

  2. print("connect success")

  3. YZVPNSettingsNetwork.bindDevice(deviceId: device ?? "")

  4. .subscribe (

  5. onNext: { response in

  6. print("websocket, 绑定成功 ")

  7. },

  8. onError: { e in

  9. print("websocket, 绑定成功 ")

  10. }

  11. )

  12. }


在连接成功后将设备信息发到我们服务器进行设备的绑定。

  1. socket.on("rows") { (data, ack) in

  2. guard let cur = data[0] as? String else { return }

  3. print(cur)

  4. }

  5. socket.on(clientEvent: .disconnect) {data, ack in

  6. print("socket disconnect")

  7. }

  8. socket.on(clientEvent: .error) {data, ack in

  9. print("socket error")

  10. }

  11. socket.on(clientEvent: .pong) {data, ack in

  12. print("socket pong" + data.description)

  13. }


接下来就是各种事件的监听,进行相应的处理。我们这边和服务端约定好定义了名叫 rows 的事件,那么在该事件中就可以收到 http/https 请求的所有信息originRequest、requestData、response信息,那么如何将每个请求的信息对应起来呢?我们这里封装了一个名叫 HttpSession 的类如下:

  1. class HttpSession : HandyJSON {


  2. var originRequest : String?

  3. var requestData : String?

  4. var response : String?

  5. var id : String?

  6. required init() {


  7. }

  8. }


返回的数据里面有 id 来作为每个 http/https 请求的唯一标示,当接收到数据时,用id进行本地映射,将数据塞入对应的 HttpSession 里面,当每个 HttpSession 对象的所有数据都被拿到时,我们再将该条请求信息展示出来。如下图所示:

以上整个本地抓包功能都已经完结,但是还有一个问题,我们去抓包肯定是打开别的 APP 将助手 App 退出到后台甚至是程序挂起状态,那么如何保证在助手 App 退出到后台模式后还能收到服务端推来的 socket 消息呢?那就需要让程序在后台长时间运行,iOS有以下几种方法:

  • VOIP

  • Background Audio 后台播放音乐

  • Location Services 定位服务

  • Newsstand downloads 后台下载

  • Remote notifications 静默推送

  • 注册一个后台任务

一般而言,音乐应用在后台是避免kill的,如果在后台应用可用时间即将为0时,播放一段音乐,就会使应用变为假前端状态。可以尝试的解决方案如下:应用申请到后台执行任务后,使用 NSTimer 开启一个定时任务,主要负责监控应用剩余的后台可执行时间,当可用的时间少于一个值时,播放一段默声音乐,然后调用 UIApplication对象的 beginBackgroundTaskWithExpirationHandler 方法将之前申请的后台执行任务结束掉,最后再重新申请一个后台执行任务,这样就可以实现后台不限时执行任务了

因此我们这边采用后台音乐和 beginBackgroundTaskWithExpirationHandler 方法保证 APP 能在后台不被 kill 。

五、总结

以上就是整个本地抓包涉及到的所有技术点,总结一下App如何完成本地抓包的流程:

  1. 当App通过VpnService/NetworkExtension配置 VPN 和定制、扩展核心网络功能

  2. 通过 tun2Socks,将请求 TCP 数据外包一层 socks 协议头,到达 socks 代理服务器后,脱去 socks 头,然后通过 socks 服务器与真实服务器之间建立的连接将 TCP 数据传给真实服务器,然后进行 ZanProxy 代理访问,完成一次http/https的完整请求

  3. 在整个请求过程中,我们用 Socket.IO 将服务端与 助手App 建立长连接,将访问的 http/https 数据实时推送给App端

  4. 助手App监听长连接事件,拿到数据进行处理,展示出来

由于篇幅有限,期间还涉及到一些其他的技术点,有兴趣的同学可以去查阅对应的文献资料,也欢迎大家积极参沟通,写出这篇文章,是抛砖引玉,也是为了其他感兴趣的同学提供一些思路。


参考文献:

https://socket.io/docs/logging-and-debugging/

https://socket.io/

扩展阅读

  1. 浅谈 Android Dex 文件

  2. 基于weex的有赞无线开发框架

  3. 有赞微商城-Android 组件化方案

-The End-

Vol.199










有赞技术团队

为 442 万商家,150 个行业,330 亿电商交易额

提供技术支持


微商城|零售|美业 | 教育


微信公众号:有赞coder    微博:@有赞技术

技术博客:tech.youzan.com





The bigger the dream, 

the more important the team.

: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存